release: dev → prod — 2026-07-17 #3 (booking lifecycle + request-list perf + refund hardening) - #1016
Conversation
…ial reschedule, cleanup guards, utilization re-key, reminder scheduler (#1002) * fix(booking): lifecycle correctness — cancelled-event CAS, class partial reschedule, cleanup guards, utilization re-key, reminder scheduler C2: webinar/class allocation now rides WHERE-guarded transitions (EVENT_ALLOWED_FROM) so a cancel racing an allocation can no longer resurrect a CANCELLED event. R1: the slotIds reschedule branch covers CLASS, ending the silent escalation of a per-session class reschedule to the whole class. R3: tentative-slot cleanup measures grace from the last write (rescheduled slots had zero grace) and skips SCHEDULED/ IN_PROGRESS webinars and classes mid-reschedule. M4: BookingUtilization substitutes re-created appointment ids one-for-one instead of re-debiting every re-allocation. The appointment-reminder job finally gets a scheduler (hourly GH Actions workflow; all three layers existed with nothing firing them). Findings C2/R1/R3/M4 + reminder gap from the 2026-07-17 booking audit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(booking): add updatedAt to the cleanup-guard fixture The sweep now measures grace from updatedAt; the #829 guard fixture predated the field and threw before the delete ran. Part of #1002. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ocate (#1000) * perf(bookings): narrow PENDING request queries + server-side Auto Allocate Phase 0 (#997): the consultations/subscriptions list GETs joined consultantProfile.domain/subDomains/tags and a per-slot user M2M that no list consumer reads, and over-shared user PII (email/role/phone) against the #946 allowlist direction. Replace the include trees with narrow selects of the verified field superset and filter ownership on the plan's indexed scalar FK. Existing composite indexes already cover the hot path — no schema change. Phase 1 (#997): the dialog's Auto Allocate no longer downloads the full scheduling period of availability to run the client algorithm; it calls the server's hardened isAuto mode (Redis locks, tz-aware caps, initialAllocation guard, idempotent replay) and reflects the returned appointments on the grid. AllocationAlgorithms.autoAllocate is retained as the mode-parity test oracle until phases 2-3. Part of #997. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(bookings): extract shared list SELECT fragments Sonar's new-code duplication gate (25% vs 3%) flagged the select trees copy-pasted between the two list routes; they must stay field-identical for shared consumers, so they now live in lib/booking/list-selects.ts. Part of #1000. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…1015) Release #1014 review, on code from #1001: - Phase-3a gateway-id binding replaced the Refund row's metadata JSON wholesale, destroying Phase-1 audit keys (initiatedByUserId, source) on every Razorpay refund (notes always present). Now spreads the reserved row's metadata under the gateway keys. - refundId binding gains the same falsy-guard as the FAILED branch, so the unique non-nullable column never gets "" and the pending_ placeholder stays matchable by reconcile-pending-refunds. - RefundResult.gatewayRefundId is now optional and normalized with `|| undefined` at both return sites: absent, never "". No caller consumed the field; the reconcile cron keys off the DB column. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
✅ Deploy Preview for familiarise ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Code Review
This pull request optimizes the booking and payment workflows by moving auto-allocation to the server-side, narrowing Prisma queries to protect user PII, and introducing guarded status transitions for webinars and classes. It also fixes issues with per-session class reschedules, double-debiting during re-allocation, and tentative slot cleanup grace periods. Feedback is provided regarding a TypeScript compilation error in the refund operation where spreading reserved.metadata requires an explicit cast to Record<string, unknown> under strict mode.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| ...(reserved.metadata && | ||
| typeof reserved.metadata === "object" && | ||
| !Array.isArray(reserved.metadata) | ||
| ? reserved.metadata | ||
| : {}), |
There was a problem hiding this comment.
TypeScript will raise a compilation error here under strict mode (Spread types may only be created from object types. ts(2698)) because reserved.metadata is typed as Prisma.JsonValue (which can be a primitive, array, or null) and cannot be directly spread. Casting it to Record<string, unknown> after the type guard resolves this issue.
| ...(reserved.metadata && | |
| typeof reserved.metadata === "object" && | |
| !Array.isArray(reserved.metadata) | |
| ? reserved.metadata | |
| : {}), | |
| ...(reserved.metadata && | |
| typeof reserved.metadata === "object" && | |
| !Array.isArray(reserved.metadata) | |
| ? (reserved.metadata as Record<string, unknown>) | |
| : {}), |




Third release of 2026-07-17, completing the day's booking-and-money wave.
#1002 — booking lifecycle correctness. Cancelled webinars and classes can no longer be resurrected by a late allocation (WHERE-guarded status transitions); class partial reschedules now reschedule only the selected sessions instead of silently escalating to the whole class; the tentative-slot cleanup measures its grace period from the last write and guards group events, so rescheduled slots are no longer swept immediately; org-sponsored re-allocations no longer double-debit BookingUtilization; and the appointment-reminder job — which had no scheduler at all — now runs hourly via GitHub Actions.
#1000 — request-list performance (#997 phases 0–1). The PENDING request endpoints replace their deep include trees with narrow field-verified selects (the old tree joined consultant domain/subdomain/tag M2Ms and per-slot user M2Ms no consumer reads, and over-shared user PII), with shared SELECT fragments in lib/booking/list-selects.ts; Auto Allocate now runs the server's hardened allocator instead of downloading the full scheduling period to the browser.
#1015 — refund hardening (review follow-up to #1014). The gateway-id binding now merges refund metadata instead of replacing it (the replacement destroyed audit keys on every refund), and empty gateway refund ids are normalized to absent rather than masquerading as real ids.
No database schema changes or migrations are included.
🤖 Generated with Claude Code